๐Ÿ Gestione degli Errori in Python

Guida Completa e Approfondita: Try, Except, Else, Finally e Oltre

๐Ÿ“‹ 1. Introduzione Approfondita

1.1 Cosa sono le Eccezioni?

Un'eccezione รจ un evento che si verifica durante l'esecuzione di un programma e che interrompe il normale flusso delle istruzioni. In Python, quando si verifica un errore durante l'esecuzione, viene "sollevata" (raised) un'eccezione. Se questa eccezione non viene gestita, il programma si interrompe e viene visualizzato un messaggio di errore chiamato traceback.

Esempio di eccezione non gestita:
# Questo codice genera un errore numeri = [1, 2, 3] print(numeri[5]) # IndexError: list index out of range
Traceback (most recent call last): File "esempio.py", line 2, in <module> print(numeri[5]) IndexError: list index out of range

1.2 Errori vs Eccezioni

รˆ importante distinguere tra errori di sintassi ed eccezioni:

โŒ Errori di Sintassi

  • Rilevati prima dell'esecuzione
  • Il programma non puรฒ essere eseguito
  • Dovuti a codice scritto male
  • Non possono essere gestiti con try-except
if True # Manca i due punti print("Errore")

โœ… Eccezioni

  • Si verificano durante l'esecuzione
  • Codice sintatticamente corretto
  • Dovute a condizioni impreviste
  • Possono essere gestite con try-except
if True: print(10 / 0) # ZeroDivisionError

1.3 Perchรฉ Gestire le Eccezioni?

๐Ÿ’ก Motivazioni Fondamentali
  1. Robustezza: Il programma continua a funzionare anche in presenza di errori
  2. User Experience: Messaggi di errore comprensibili invece di traceback tecnici
  3. Debugging: Logging degli errori per analisi successive
  4. Pulizia Risorse: Garantire la chiusura di file, connessioni database, ecc.
  5. Flusso di Controllo: Gestire scenari alternativi in modo elegante
  6. Sicurezza: Evitare che informazioni sensibili vengano esposte nei messaggi di errore

๐Ÿ”ง 2. Il Blocco Try-Except: Fondamenti

2.1 Sintassi Base e Funzionamento

Il meccanismo try-except permette di "provare" (try) ad eseguire del codice e "catturare" (except) eventuali eccezioni che si verificano.

try: # Blocco di codice da provare # Se si verifica un'eccezione, l'esecuzione salta al blocco except codice_rischioso() except: # Blocco eseguito solo se si verifica un'eccezione nel try gestione_errore()

2.2 Flusso di Esecuzione Dettagliato

1. Il programma entra nel blocco TRY
โ†“
2. Esegue le istruzioni sequenzialmente
โ†“
3a. Si verifica un'eccezione?
SรŒ โ†’ Salta immediatamente al blocco EXCEPT
NO โ†’ Continua fino alla fine del TRY
โ†“
4. Se nessuna eccezione: salta il blocco EXCEPT
โ†“
5. Continua con il codice successivo

2.3 Esempio Dettagliato Passo-Passo

Analisi del flusso di esecuzione:
print("1. Inizio programma") try: print("2. Entrato nel blocco try") risultato = 10 / 2 print(f"3. Risultato: {risultato}") risultato = 10 / 0 # Qui si verifica l'eccezione! print("4. Questa riga NON viene eseguita") except: print("5. Gestione errore nel blocco except") print("6. Fine programma")
1. Inizio programma 2. Entrato nel blocco try 3. Risultato: 5.0 5. Gestione errore nel blocco except 6. Fine programma
Nota Importante: Quando si verifica un'eccezione nel blocco try, l'esecuzione viene immediatamente trasferita al blocco except. Le istruzioni successive nel try vengono saltate completamente.

๐ŸŽฏ 3. Catturare Eccezioni Specifiche

3.1 Perchรฉ Essere Specifici?

โš ๏ธ Problema con Except Generico

Usare except: senza specificare il tipo di eccezione cattura TUTTE le eccezioni, incluse quelle di sistema come KeyboardInterrupt (Ctrl+C). Questo รจ considerato una cattiva pratica perchรฉ:

  • Nasconde bug nel codice
  • Rende difficile il debugging
  • Puรฒ catturare eccezioni che non dovrebbero essere gestite
  • Impedisce l'interruzione volontaria del programma

3.2 Sintassi per Eccezioni Specifiche

try: operazione_rischiosa() except ValueError: # Gestisce solo ValueError print("Errore di valore") except TypeError: # Gestisce solo TypeError print("Errore di tipo") except (IndexError, KeyError): # Gestisce sia IndexError che KeyError print("Errore di indice o chiave") except Exception as e: # Cattura tutte le altre eccezioni print(f"Errore generico: {e}")

3.3 Accedere ai Dettagli dell'Eccezione

Usando la sintassi except TipoEccezione as nome_variabile, possiamo accedere all'oggetto eccezione e ottenere informazioni dettagliate:

Accesso completo alle informazioni dell'eccezione:
import sys try: numero = int("abc") except ValueError as e: print(f"Tipo di eccezione: {type(e).__name__}") print(f"Messaggio: {e}") print(f"Argomenti: {e.args}") # Informazioni sul traceback exc_type, exc_value, exc_traceback = sys.exc_info() print(f"Linea: {exc_traceback.tb_lineno}")
Tipo di eccezione: ValueError Messaggio: invalid literal for int() with base 10: 'abc' Argomenti: ("invalid literal for int() with base 10: 'abc'",) Linea: 4

3.4 Gerarchia delle Eccezioni in Python

Le eccezioni in Python sono organizzate in una gerarchia di classi. Quando si cattura un'eccezione, vengono catturate anche tutte le sue sottoclassi:

BaseException โ”œโ”€โ”€ SystemExit โ”œโ”€โ”€ KeyboardInterrupt โ”œโ”€โ”€ GeneratorExit โ””โ”€โ”€ Exception โ”œโ”€โ”€ StopIteration โ”œโ”€โ”€ ArithmeticError โ”‚ โ”œโ”€โ”€ FloatingPointError โ”‚ โ”œโ”€โ”€ OverflowError โ”‚ โ””โ”€โ”€ ZeroDivisionError โ”œโ”€โ”€ LookupError โ”‚ โ”œโ”€โ”€ IndexError โ”‚ โ””โ”€โ”€ KeyError โ”œโ”€โ”€ ValueError โ”œโ”€โ”€ TypeError โ”œโ”€โ”€ OSError โ”‚ โ”œโ”€โ”€ FileNotFoundError โ”‚ โ”œโ”€โ”€ PermissionError โ”‚ โ””โ”€โ”€ ConnectionError โ””โ”€โ”€ ... (molte altre)
โš ๏ธ Ordine dei Blocchi Except

L'ordine dei blocchi except รจ importante! Le eccezioni piรน specifiche devono essere catturate prima di quelle piรน generali:

# โœ… CORRETTO: dal piรน specifico al piรน generico try: operazione() except FileNotFoundError: # Specifica gestisci_file_non_trovato() except OSError: # Piรน generica gestisci_errore_os() except Exception: # Generica gestisci_errore_generico() # โŒ SBAGLIATO: il primo except catturerebbe tutto try: operazione() except Exception: # Troppo presto! gestisci_errore_generico() except FileNotFoundError: # Non verrร  mai eseguito! gestisci_file_non_trovato()

3.5 Tabella Completa delle Eccezioni Comuni

Eccezione Descrizione Dettagliata Quando si Verifica Esempio
ValueError Funzione riceve argomento del tipo giusto ma valore inappropriato Conversioni fallite, valori fuori range int("abc"), math.sqrt(-1)
TypeError Operazione applicata a oggetto di tipo inappropriato Operazioni tra tipi incompatibili "5" + 5, len(5)
ZeroDivisionError Divisione o modulo per zero Operazioni matematiche con divisore zero 10 / 0, 15 % 0
IndexError Indice di sequenza fuori range Accesso a indici non esistenti in liste/tuple lista[100] con lista di 10 elementi
KeyError Chiave non trovata in dizionario Accesso a chiave inesistente dict["chiave_assente"]
AttributeError Attributo o metodo non esiste per l'oggetto Accesso a attributi inesistenti stringa.append(), numero.upper()
FileNotFoundError File o directory non trovati Apertura file inesistente open("file_inesistente.txt")
PermissionError Permessi insufficienti per l'operazione Accesso file/directory senza permessi open("/root/file.txt", "w")
ImportError Modulo non trovato o errore nell'import Import di modulo non installato/esistente import modulo_inesistente
NameError Nome variabile non definito Uso di variabile non dichiarata print(variabile_non_definita)
MemoryError Memoria insufficiente Allocazione troppo grande lista = [0] * (10**10)
RuntimeError Errore generico runtime non coperto da altri Errori non categorizzabili altrimenti Ricorsione troppo profonda

โœจ 4. Il Blocco Else: Esecuzione Condizionale

4.1 Scopo e Funzionamento

Il blocco else viene eseguito solo se NON si sono verificate eccezioni nel blocco try. Questo รจ utile per separare il codice che potrebbe generare eccezioni dal codice che dovrebbe essere eseguito solo in caso di successo.

TRY: Esegue il codice
โ†“
Eccezione?
SรŒ โ†’ EXCEPT | NO โ†’ ELSE
โ†“
FINALLY: Eseguito sempre

4.2 Vantaggi dell'uso di Else

โœ… Perchรฉ usare Else invece di mettere tutto nel Try?
  1. Chiarezza: Separa il codice rischioso da quello sicuro
  2. Precisione: Solo il codice che puรฒ generare eccezioni sta nel try
  3. Debugging: Piรน facile identificare la fonte degli errori
  4. Performance: Il blocco try ha un piccolo overhead, meglio tenerlo minimale

4.3 Esempio Comparativo

โŒ Senza Else (meno chiaro)

try: file = open("dati.txt") contenuto = file.read() # Questo non dovrebbe stare nel try! elabora_dati(contenuto) salva_risultati() invia_notifica() except FileNotFoundError: print("File non trovato")

โœ… Con Else (piรน chiaro)

try: file = open("dati.txt") contenuto = file.read() except FileNotFoundError: print("File non trovato") else: # Eseguito solo se apertura riuscita elabora_dati(contenuto) salva_risultati() invia_notifica()

4.4 Esempio Pratico Completo

Sistema di login con else:
def verifica_login(username, password): try: # Simulazione verifica credenziali if len(username) < 3: raise ValueError("Username troppo corto") if len(password) < 6: raise ValueError("Password troppo corta") # Simulazione controllo database utente_valido = verifica_database(username, password) except ValueError as e: print(f"โŒ Errore validazione: {e}") return False except ConnectionError: print("โŒ Errore connessione database") return False else: # Eseguito solo se nessuna eccezione print(f"โœ… Login riuscito per {username}") crea_sessione(username) registra_accesso(username) invia_notifica_sicurezza(username) return True

๐Ÿ”„ 5. Il Blocco Finally: Garanzia di Esecuzione

5.1 Caratteristiche Fondamentali

Il blocco finally รจ il blocco piรน importante per la gestione delle risorse. Viene SEMPRE eseguito, indipendentemente da:

๐Ÿ’ก Quando viene eseguito Finally?
# Finally viene SEMPRE eseguito in questi casi: try: return valore # Finally eseguito prima del return! finally: cleanup() try: break # Finally eseguito prima del break! finally: cleanup() try: raise Exception() # Finally eseguito anche con eccezione non gestita! finally: cleanup()

5.2 Casi d'Uso Principali

5.2.1 Gestione File

Garanzia di chiusura file:
def processa_file(nome_file): file = None try: print("1. Apertura file...") file = open(nome_file, 'r') print("2. Lettura contenuto...") contenuto = file.read() print("3. Elaborazione...") risultato = elabora(contenuto) return risultato except FileNotFoundError: print("โŒ File non trovato") return None except PermissionError: print("โŒ Permessi insufficienti") return None finally: # SEMPRE eseguito, anche se c'รจ return! if file is not None: print("4. Chiusura file...") file.close() print("5. Cleanup completato")

5.2.2 Connessioni Database

Garanzia di chiusura connessione:
def esegui_query(query): connessione = None try: connessione = crea_connessione_db() cursor = connessione.cursor() cursor.execute(query) risultati = cursor.fetchall() connessione.commit() return risultati except DatabaseError as e: if connessione: connessione.rollback() print(f"Errore database: {e}") return [] finally: # Chiude SEMPRE la connessione if connessione: connessione.close() print("Connessione chiusa")

5.2.3 Lock e Sincronizzazione

Rilascio garantito di lock:
import threading lock = threading.Lock() def operazione_critica(): try: lock.acquire() print("Lock acquisito") # Operazioni sulla risorsa condivisa risorsa_condivisa.modifica() except Exception as e: print(f"Errore: {e}") finally: # Lock SEMPRE rilasciato lock.release() print("Lock rilasciato")

5.3 Finally vs Context Manager

Python offre i context manager (with statement) come alternativa piรน elegante per la gestione delle risorse:

Con Finally (approccio tradizionale)

file = None try: file = open("dati.txt") contenuto = file.read() print(contenuto) except FileNotFoundError: print("File non trovato") finally: if file: file.close()

Con Context Manager (approccio moderno)

try: with open("dati.txt") as file: contenuto = file.read() print(contenuto) # File chiuso automaticamente! except FileNotFoundError: print("File non trovato")
Nota: Il context manager (with) รจ preferibile quando disponibile, ma finally rimane essenziale per situazioni piรน complesse o quando non esiste un context manager per la risorsa da gestire.

๐Ÿ“Š 6. Struttura Completa Try-Except-Else-Finally

6.1 Schema Completo

try: # 1. Codice che potrebbe generare eccezioni # Mantienilo il piรน breve possibile operazione_rischiosa() except SpecificException1 as e: # 2. Gestione eccezione specifica 1 # Eseguito solo se si verifica SpecificException1 gestisci_eccezione1(e) except SpecificException2 as e: # 3. Gestione eccezione specifica 2 # Eseguito solo se si verifica SpecificException2 gestisci_eccezione2(e) except (Exception3, Exception4) as e: # 4. Gestione multipla # Eseguito per Exception3 O Exception4 gestisci_multiple(e) except Exception as e: # 5. Catch-all (sempre per ultimo!) # Cattura tutte le altre eccezioni gestisci_generica(e) else: # 6. Eseguito SOLO se NON ci sono eccezioni # Codice da eseguire in caso di successo operazioni_successo() finally: # 7. SEMPRE eseguito # Pulizia risorse, logging, chiusure cleanup()

6.2 Ordine di Esecuzione: Tutti i Possibili Scenari

Scenario 1: Nessuna Eccezione

try: print("1. Try") except: print("2. Except - NON eseguito") else: print("3. Else - ESEGUITO") finally: print("4. Finally - ESEGUITO")
1. Try 3. Else - ESEGUITO 4. Finally - ESEGUITO

Scenario 2: Eccezione Gestita

try: print("1. Try") raise ValueError() except ValueError: print("2. Except - ESEGUITO") else: print("3. Else - NON eseguito") finally: print("4. Finally - ESEGUITO")
1. Try 2. Except - ESEGUITO 4. Finally - ESEGUITO

Scenario 3: Eccezione NON Gestita

try: print("1. Try") raise KeyError() # Non gestita! except ValueError: # Non cattura KeyError print("2. Except - NON eseguito") else: print("3. Else - NON eseguito") finally: print("4. Finally - ESEGUITO") # Poi l'eccezione viene propagata
1. Try 4. Finally - ESEGUITO KeyError (poi l'eccezione viene sollevata)

6.3 Esempio Reale Completo: Sistema di Pagamento

Sistema di elaborazione pagamenti con gestione completa:
import logging from datetime import datetime def elabora_pagamento(id_utente, importo, carta): """ Elabora un pagamento con gestione completa degli errori. Returns: dict: Risultato dell'operazione con stato e dettagli """ transazione_id = genera_id_transazione() connessione = None lock_acquisito = False # Logging iniziale logging.info(f"Inizio transazione {transazione_id} per utente {id_utente}") try: # 1. Validazione input if importo <= 0: raise ValueError(f"Importo non valido: {importo}") if not valida_carta(carta): raise ValueError("Carta non valida") # 2. Acquisizione risorse lock_acquisito = acquisisci_lock_utente(id_utente) connessione = connetti_payment_gateway() # 3. Verifica disponibilitร  if not verifica_fondi(carta, importo): raise InsufficientFundsError("Fondi insufficienti") # 4. Elaborazione pagamento risultato = connessione.processa_pagamento( carta=carta, importo=importo, transazione_id=transazione_id ) # 5. Verifica risposta if not risultato.success: raise PaymentGatewayError(risultato.error_message) except ValueError as e: # Errori di validazione logging.error(f"Validazione fallita: {e}") registra_transazione_fallita(transazione_id, "VALIDATION_ERROR", str(e)) return { 'success': False, 'error_type': 'VALIDATION_ERROR', 'message': str(e), 'transazione_id': transazione_id } except InsufficientFundsError as e: # Fondi insufficienti logging.warning(f"Fondi insufficienti: {e}") registra_transazione_fallita(transazione_id, "INSUFFICIENT_FUNDS", str(e)) return { 'success': False, 'error_type': 'INSUFFICIENT_FUNDS', 'message': "Fondi insufficienti sulla carta", 'transazione_id': transazione_id } except PaymentGatewayError as e: # Errore dal gateway di pagamento logging.error(f"Errore gateway: {e}") registra_transazione_fallita(transazione_id, "GATEWAY_ERROR", str(e)) invia_alert_tecnico(e) return { 'success': False, 'error_type': 'GATEWAY_ERROR', 'message': "Errore temporaneo. Riprova piรน tardi.", 'transazione_id': transazione_id } except ConnectionError as e: # Errore di connessione logging.critical(f"Errore connessione: {e}") registra_transazione_fallita(transazione_id, "CONNECTION_ERROR", str(e)) invia_alert_urgente(e) return { 'success': False, 'error_type': 'CONNECTION_ERROR', 'message': "Servizio temporaneamente non disponibile", 'transazione_id': transazione_id } except Exception as e: # Errore inaspettato logging.critical(f"Errore inaspettato: {type(e).__name__} - {e}") registra_transazione_fallita(transazione_id, "UNEXPECTED_ERROR", str(e)) invia_alert_urgente(e) return { 'success': False, 'error_type': 'SYSTEM_ERROR', 'message': "Errore di sistema. Contatta l'assistenza.", 'transazione_id': transazione_id } else: # Successo! Eseguito solo se nessuna eccezione logging.info(f"Pagamento completato: {transazione_id}") # Operazioni post-pagamento aggiorna_saldo_utente(id_utente, importo) registra_transazione_successo(transazione_id, risultato) invia_ricevuta_email(id_utente, transazione_id, importo) aggiorna_statistiche(id_utente) return { 'success': True, 'transazione_id': transazione_id, 'importo': importo, 'timestamp': datetime.now().isoformat(), 'reference': risultato.reference_number } finally: # Pulizia SEMPRE eseguita if lock_acquisito: rilascia_lock_utente(id_utente) logging.debug(f"Lock rilasciato per utente {id_utente}") if connessione: connessione.close() logging.debug("Connessione chiusa") # Logging finale logging.info(f"Fine elaborazione transazione {transazione_id}")

๐Ÿš€ 7. Tecniche Avanzate

7.1 Re-raising di Eccezioni

A volte vogliamo catturare un'eccezione, eseguire alcune operazioni (come logging), e poi rilanciarla per farla gestire a un livello superiore:

def operazione_critica(): try: risultato = operazione_database() return risultato except DatabaseError as e: # Logging dell'errore logging.error(f"Errore database: {e}") invia_notifica_admin(e) # Re-raise: rilancia la stessa eccezione raise # Nota: nessun argomento! # Ora un altro livello puรฒ gestire l'eccezione try: operazione_critica() except DatabaseError: print("Gestione a livello superiore")
โš ๏ธ Raise vs Raise e
# โœ… CORRETTO: mantiene il traceback originale except Exception as e: log_error(e) raise # Rilancia con traceback completo # โŒ SBAGLIATO: perde il traceback originale except Exception as e: log_error(e) raise e # Il traceback ricomincia da qui!

7.2 Concatenazione di Eccezioni (Exception Chaining)

Python 3 permette di associare un'eccezione alla sua causa originale usando raise ... from ...:

Exception chaining con from:
class ConfigError(Exception): """Errore di configurazione dell'applicazione""" pass def carica_configurazione(file_config): try: with open(file_config) as f: config = json.load(f) return config except FileNotFoundError as e: # Solleva una nuova eccezione collegata all'originale raise ConfigError( f"File di configurazione '{file_config}' non trovato" ) from e except json.JSONDecodeError as e: raise ConfigError( f"Formato JSON non valido: {e.msg} alla riga {e.lineno}" ) from e # Uso: try: config = carica_configurazione("config.json") except ConfigError as e: print(f"Errore: {e}") print(f"Causa: {e.__cause__}") # Accesso all'eccezione originale
๐Ÿ’ก Vantaggi dell'Exception Chaining
  • Mantiene la traccia completa degli errori
  • Fornisce contesto a diversi livelli di astrazione
  • Facilita il debugging mostrando la catena di cause
  • Permette di trasformare eccezioni tecniche in errori piรน comprensibili

7.3 Soppressione del Contesto di Eccezioni

Se vuoi sollevare una nuova eccezione senza mostrare quella originale, usa raise ... from None:

try: valore = dizionario["chiave"] except KeyError: # Solleva un'eccezione piรน user-friendly senza mostrare il KeyError raise ValueError("Configurazione mancante") from None

7.4 Creazione di Eccezioni Personalizzate

Creare eccezioni personalizzate rende il codice piรน leggibile e permette gestione granulare degli errori:

Sistema completo di eccezioni personalizzate:
# Gerarchia di eccezioni personalizzate class ApplicationError(Exception): """Classe base per tutte le eccezioni dell'applicazione""" def __init__(self, message, error_code=None, details=None): super().__init__(message) self.message = message self.error_code = error_code self.details = details or {} self.timestamp = datetime.now() def to_dict(self): """Converte l'eccezione in dizionario per logging/API""" return { 'error_type': self.__class__.__name__, 'message': self.message, 'error_code': self.error_code, 'details': self.details, 'timestamp': self.timestamp.isoformat() } class ValidationError(ApplicationError): """Errore di validazione input""" def __init__(self, message, field=None): super().__init__(message, error_code="VAL_001") self.field = field if field: self.details['field'] = field class AuthenticationError(ApplicationError): """Errore di autenticazione""" def __init__(self, message, user_id=None): super().__init__(message, error_code="AUTH_001") if user_id: self.details['user_id'] = user_id class ResourceNotFoundError(ApplicationError): """Risorsa non trovata""" def __init__(self, resource_type, resource_id): message = f"{resource_type} con ID {resource_id} non trovato" super().__init__(message, error_code="RES_404") self.details['resource_type'] = resource_type self.details['resource_id'] = resource_id class BusinessRuleError(ApplicationError): """Violazione regola di business""" def __init__(self, message, rule_name): super().__init__(message, error_code="BUS_001") self.details['rule'] = rule_name # Esempio di utilizzo def crea_utente(username, email, age): try: # Validazioni if len(username) < 3: raise ValidationError( "Username troppo corto", field="username" ) if age < 18: raise BusinessRuleError( "Etร  minima richiesta: 18 anni", rule_name="minimum_age" ) if utente_esiste(email): raise BusinessRuleError( "Email giร  registrata", rule_name="unique_email" ) # Creazione utente utente = salva_utente(username, email, age) return utente except ValidationError as e: logging.warning(f"Validazione fallita: {e.to_dict()}") raise except BusinessRuleError as e: logging.info(f"Regola violata: {e.to_dict()}") raise except ApplicationError as e: logging.error(f"Errore applicazione: {e.to_dict()}") raise except Exception as e: logging.critical(f"Errore inaspettato: {e}") raise ApplicationError( "Errore interno del server", error_code="SYS_500" ) from e

7.5 Context Manager Personalizzato

Puoi creare context manager personalizzati per gestire risorse in modo elegante:

Context manager per gestione timer:
import time from contextlib import contextmanager @contextmanager def timer(nome_operazione): """Context manager per misurare tempo di esecuzione""" inizio = time.time() print(f"โฑ๏ธ Inizio: {nome_operazione}") try: yield except Exception as e: print(f"โŒ Errore durante {nome_operazione}: {e}") raise finally: fine = time.time() durata = fine - inizio print(f"โœ… Fine: {nome_operazione} (durata: {durata:.2f}s)") # Uso: with timer("Elaborazione dati"): # Codice da misurare processa_file("dati.csv") analizza_risultati()

7.6 Decoratori per Gestione Errori

Decoratore per retry automatico:
import time from functools import wraps def retry(max_tentativi=3, delay=1, eccezioni=(Exception,)): """ Decoratore per ritentare una funzione in caso di errore. Args: max_tentativi: numero massimo di tentativi delay: secondi di attesa tra i tentativi eccezioni: tuple di eccezioni da catturare """ def decorator(func): @wraps(func) def wrapper(*args, **kwargs): ultimo_errore = None for tentativo in range(1, max_tentativi + 1): try: risultato = func(*args, **kwargs) if tentativo > 1: print(f"โœ… Successo al tentativo {tentativo}") return risultato except eccezioni as e: ultimo_errore = e print(f"โš ๏ธ Tentativo {tentativo}/{max_tentativi} fallito: {e}") if tentativo < max_tentativi: print(f"โณ Attendo {delay}s prima di ritentare...") time.sleep(delay) # Tutti i tentativi falliti print(f"โŒ Tutti i {max_tentativi} tentativi falliti") raise ultimo_errore return wrapper return decorator # Uso del decoratore: @retry(max_tentativi=5, delay=2, eccezioni=(ConnectionError, TimeoutError)) def connetti_api(url): """Tenta connessione all'API con retry automatico""" risposta = requests.get(url, timeout=5) risposta.raise_for_status() return risposta.json() # La funzione ritenterร  automaticamente in caso di errore try: dati = connetti_api("https://api.esempio.com/dati") except Exception as e: print(f"Impossibile ottenere dati: {e}")

โš ๏ธ 8. Best Practices e Antipattern

8.1 Best Practices

โœ… Cosa FARE
  1. Cattura eccezioni specifiche:
    # โœ… BUONO try: numero = int(input_utente) except ValueError: print("Input non valido")
  2. Usa finally per cleanup:
    # โœ… BUONO file = None try: file = open("data.txt") processa(file) finally: if file: file.close()
  3. Fornisci messaggi di errore chiari:
    # โœ… BUONO if age < 0: raise ValueError(f"Etร  non valida: {age}. Deve essere >= 0")
  4. Documenta le eccezioni sollevate:
    # โœ… BUONO def leggi_file(percorso): """ Legge il contenuto di un file. Args: percorso (str): Percorso del file Returns: str: Contenuto del file Raises: FileNotFoundError: Se il file non esiste PermissionError: Se mancano i permessi UnicodeDecodeError: Se il file non รจ UTF-8 """ with open(percorso, encoding='utf-8') as f: return f.read()
  5. Logga gli errori appropriatamente:
    # โœ… BUONO import logging try: operazione_critica() except Exception as e: logging.exception("Errore in operazione_critica") # logging.exception include automaticamente il traceback

8.2 Antipattern da Evitare

โŒ Cosa NON FARE

1. Except Generico Silenzioso (Silent Failure)

# โŒ PESSIMO - Nasconde tutti gli errori! try: operazione_rischiosa() except: pass # Ignora completamente l'errore # โœ… MEGLIO try: operazione_rischiosa() except SpecificError as e: logging.warning(f"Errore previsto: {e}") # Gestisci appropriatamente

2. Catturare Eccezioni di Sistema

# โŒ SBAGLIATO - Cattura anche KeyboardInterrupt! try: while True: processa() except: # Cattura TUTTO, incluso Ctrl+C print("Errore") # โœ… CORRETTO try: while True: processa() except KeyboardInterrupt: print("Interruzione utente") raise # Permette l'interruzione except Exception as e: print(f"Errore: {e}")

3. Blocchi Try Troppo Ampi

# โŒ SBAGLIATO - Try troppo ampio try: # 100 righe di codice... operazione1() operazione2() operazione3() # ... altro codice except Exception: print("Errore") # Ma dove?? # โœ… MEGLIO - Try specifici try: operazione1() except Error1: gestisci_error1() try: operazione2() except Error2: gestisci_error2()

4. Ritorno di Valori "Magici" invece di Eccezioni

# โŒ ANTIPATTERN def dividi(a, b): if b == 0: return -1 # Valore "magico" per errore return a / b # โœ… MEGLIO def dividi(a, b): if b == 0: raise ValueError("Divisione per zero") return a / b

5. Usare Eccezioni per il Flusso di Controllo Normale

# โŒ SBAGLIATO - Eccezioni non sono goto! try: for i in range(100): if i == 50: raise StopIteration # NO! processa(i) except StopIteration: pass # โœ… CORRETTO for i in range(50): processa(i)

6. Perdere Informazioni dell'Eccezione

# โŒ SBAGLIATO except SpecificError: raise Exception("Errore generico") # Perde dettagli! # โœ… MEGLIO except SpecificError as e: raise CustomError(f"Dettagli: {e}") from e

๐ŸŽฎ 9. Demo Interattiva Avanzata

๐ŸŽฒ Simulatore di Scenari di Errore

Clicca per testare diversi scenari di gestione degli errori

๐Ÿ“ 10. Esercizi Pratici Avanzati

๐ŸŽ“ Esercizio 1: Sistema di Registrazione Utenti

Obiettivo: Crea una funzione completa di registrazione con validazione e gestione errori.

Requisiti:

  • Validare username (min 3 caratteri, alfanumerico)
  • Validare email (formato corretto)
  • Validare password (min 8 caratteri, almeno 1 numero e 1 maiuscola)
  • Verificare che email non sia giร  registrata
  • Gestire errori di connessione database
  • Logging di tutti gli eventi
  • Usare eccezioni personalizzate
๐ŸŽ“ Esercizio 2: Parser di File CSV con Gestione Errori

Obiettivo: Implementa un parser robusto per file CSV.

Requisiti:

  • Gestire file non trovato, permessi negati, encoding errato
  • Validare struttura CSV (numero colonne, tipi di dati)
  • Gestire righe corrotte senza interrompere il parsing
  • Implementare retry per errori di I/O temporanei
  • Generare report degli errori trovati
  • Garantire chiusura file anche in caso di errori
๐ŸŽ“ Esercizio 3: Gestore di Transazioni Bancarie

Obiettivo: Sistema di trasferimento denaro con gestione errori completa.

Requisiti:

  • Validare importi (positivi, formato corretto)
  • Verificare saldo sufficiente
  • Gestire lock per evitare race conditions
  • Implementare rollback in caso di errore
  • Logging dettagliato di ogni operazione
  • Notifiche in caso di operazioni sospette
๐ŸŽ“ Esercizio 4: Web Scraper Resiliente

Obiettivo: Scraper che gestisce tutti i possibili errori di rete.

Requisiti:

  • Gestire timeout, errori di connessione, errori HTTP
  • Implementare retry con backoff esponenziale
  • Gestire rate limiting (429 Too Many Requests)
  • Parser HTML robusto (gestire HTML malformato)
  • Cache per evitare richieste duplicate
  • Salvataggio progressivo per non perdere dati

๐Ÿ” 11. Debugging e Analisi degli Errori

11.1 Ottenere Informazioni Dettagliate

Estrazione completa delle informazioni di errore:
import sys import traceback def analizza_errore_completo(): try: # Codice che genera errore risultato = 10 / 0 except Exception as e: # 1. Informazioni base sull'eccezione print(f"Tipo: {type(e).__name__}") print(f"Messaggio: {str(e)}") print(f"Args: {e.args}") # 2. Informazioni sul traceback exc_type, exc_value, exc_traceback = sys.exc_info() print(f"\nFile: {exc_traceback.tb_frame.f_code.co_filename}") print(f"Funzione: {exc_traceback.tb_frame.f_code.co_name}") print(f"Linea: {exc_traceback.tb_lineno}") # 3. Stack trace formattato print("\nStack trace completo:") print(''.join(traceback.format_tb(exc_traceback))) # 4. Formato come apparirebbe senza try-except print("\nFormato standard:") print(''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))) # 5. Variabili locali al momento dell'errore print("\nVariabili locali:") for var_name, var_value in exc_traceback.tb_frame.f_locals.items(): print(f" {var_name} = {var_value}")

11.2 Logging Avanzato degli Errori

Sistema di logging professionale:
import logging import sys from datetime import datetime # Configurazione logging avanzata logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('app.log'), logging.StreamHandler(sys.stdout) ] ) logger = logging.getLogger(__name__) def operazione_con_logging(parametro): logger.info(f"Inizio operazione con parametro: {parametro}") try: # Operazione rischiosa risultato = esegui_operazione(parametro) except ValueError as e: logger.error( f"Errore validazione per parametro {parametro}: {e}", exc_info=True # Include traceback ) raise except ConnectionError as e: logger.critical( f"Errore connessione critico: {e}", exc_info=True, extra={ 'parametro': parametro, 'timestamp': datetime.now().isoformat() } ) invia_alert_admin() raise except Exception as e: # logging.exception include automaticamente exc_info=True logger.exception( f"Errore inaspettato in operazione: {e}" ) raise else: logger.info(f"Operazione completata con successo: {risultato}") return risultato finally: logger.debug("Cleanup risorse completato")

11.3 Sistema di Monitoraggio Errori

Tracciamento statistiche errori:
from collections import defaultdict, Counter from datetime import datetime import json class ErrorMonitor: """Sistema di monitoraggio e analisi errori""" def __init__(self): self.errori = [] self.contatori = Counter() self.errori_per_tipo = defaultdict(list) def registra_errore(self, eccezione, contesto=None): """Registra un'eccezione con contesto""" tipo_errore = type(eccezione).__name__ record = { 'timestamp': datetime.now().isoformat(), 'tipo': tipo_errore, 'messaggio': str(eccezione), 'contesto': contesto or {} } self.errori.append(record) self.contatori[tipo_errore] += 1 self.errori_per_tipo[tipo_errore].append(record) def ottieni_statistiche(self): """Genera statistiche sugli errori""" return { 'totale_errori': len(self.errori), 'errori_per_tipo': dict(self.contatori), 'errore_piu_comune': self.contatori.most_common(1)[0] if self.contatori else None, 'ultimo_errore': self.errori[-1] if self.errori else None } def esporta_report(self, file_path): """Esporta report completo in JSON""" report = { 'statistiche': self.ottieni_statistiche(), 'tutti_errori': self.errori } with open(file_path, 'w') as f: json.dump(report, f, indent=2) # Uso del monitor monitor = ErrorMonitor() def operazione_monitorata(dati): try: risultato = processa(dati) return risultato except Exception as e: monitor.registra_errore(e, contesto={ 'funzione': 'operazione_monitorata', 'dati_input': str(dati)[:100] # Primi 100 caratteri }) raise # Alla fine del programma print(monitor.ottieni_statistiche()) monitor.esporta_report('error_report.json')

๐Ÿ’ก 12. Pattern Avanzati

12.1 Pattern: Nullability e Optional

Gestione valori opzionali invece di eccezioni:
from typing import Optional def trova_utente(user_id: int) -> Optional[dict]: """ Cerca un utente nel database. Returns: dict se trovato, None altrimenti (nessuna eccezione) """ try: utente = db.query(f"SELECT * FROM users WHERE id={user_id}") return utente except NotFoundError: return None # Invece di sollevare eccezione # Uso con gestione esplicita di None utente = trova_utente(123) if utente is not None: print(f"Trovato: {utente['nome']}") else: print("Utente non trovato")

12.2 Pattern: Result Type

Ritornare Success o Error invece di eccezioni:
from dataclasses import dataclass from typing import Generic, TypeVar, Union T = TypeVar('T') E = TypeVar('E') @dataclass class Success(Generic[T]): """Rappresenta un'operazione riuscita""" value: T def is_success(self) -> bool: return True def is_error(self) -> bool: return False @dataclass class Error(Generic[E]): """Rappresenta un errore""" error: E def is_success(self) -> bool: return False def is_error(self) -> bool: return True Result = Union[Success[T], Error[E]] # Uso del pattern Result def dividi(a: float, b: float) -> Result[float, str]: """Divisione che ritorna Result invece di sollevare eccezioni""" if b == 0: return Error("Divisione per zero") return Success(a / b) # Uso risultato = dividi(10, 2) if risultato.is_success(): print(f"Risultato: {risultato.value}") else: print(f"Errore: {risultato.error}")

12.3 Pattern: Circuit Breaker

Circuit breaker per servizi esterni:
from enum import Enum from datetime import datetime, timedelta class CircuitState(Enum): CLOSED = "closed" # Funzionante OPEN = "open" # Troppi errori, circuito aperto HALF_OPEN = "half_open" # Test se ripristinato class CircuitBreaker: """ Circuit breaker per proteggere da servizi difettosi. Previene cascate di errori. """ def __init__(self, soglia_errori=5, timeout=60): self.soglia_errori = soglia_errori self.timeout = timeout self.errori = 0 self.stato = CircuitState.CLOSED self.ultimo_errore = None def call(self, func, *args, **kwargs): """Esegue funzione attraverso il circuit breaker""" if self.stato == CircuitState.OPEN: if datetime.now() - self.ultimo_errore > timedelta(seconds=self.timeout): self.stato = CircuitState.HALF_OPEN print("๐ŸŸก Circuit HALF_OPEN: tentativo di ripristino") else: raise Exception("Circuit breaker OPEN: servizio non disponibile") try: risultato = func(*args, **kwargs) self._on_success() return risultato except Exception as e: self._on_error() raise def _on_success(self): """Gestisce successo chiamata""" if self.stato == CircuitState.HALF_OPEN: print("๐ŸŸข Circuit CLOSED: servizio ripristinato") self.stato = CircuitState.CLOSED self.errori = 0 def _on_error(self): """Gestisce errore chiamata""" self.errori += 1 self.ultimo_errore = datetime.now() if self.errori >= self.soglia_errori: self.stato = CircuitState.OPEN print(f"๐Ÿ”ด Circuit OPEN: troppi errori ({self.errori})") # Uso breaker = CircuitBreaker(soglia_errori=3, timeout=30) def chiama_api_esterna(): return breaker.call(requests.get, "https://api.example.com/data")

๐Ÿ“š 13. Risorse e Riferimenti

๐Ÿ“– Documentazione Ufficiale
  • Python Docs - Errors and Exceptions: Documentazione ufficiale completa
  • PEP 8: Guida di stile per il codice Python
  • PEP 3134: Exception Chaining and Embedded Tracebacks
  • Python Standard Library: Moduli warnings, logging, traceback
๐Ÿ”— Link Utili
  • Built-in Exceptions Hierarchy - Python.org
  • Real Python - Python Exceptions Guide
  • Effective Python by Brett Slatkin
  • Clean Code in Python - Mariano Anaya

โœ… 14. Checklist Finale

โœ… Verifica le tue Conoscenze
  • โ˜ Comprendi la differenza tra errori di sintassi ed eccezioni runtime
  • โ˜ Sai quando usare try-except e quando no
  • โ˜ Catturi sempre eccezioni specifiche invece di except generico
  • โ˜ Usi finally per garantire cleanup delle risorse
  • โ˜ Comprendi il flusso di esecuzione di try-except-else-finally
  • โ˜ Sai come accedere ai dettagli delle eccezioni
  • โ˜ Conosci la gerarchia delle eccezioni Python
  • โ˜ Documenti le eccezioni che le tue funzioni possono sollevare
  • โ˜ Implementi logging appropriato degli errori
  • โ˜ Sai quando creare eccezioni personalizzate
  • โ˜ Comprendi exception chaining e re-raising
  • โ˜ Eviti gli antipattern comuni
  • โ˜ Usi context manager quando appropriato
  • โ˜ Conosci pattern avanzati come Circuit Breaker
  • โ˜ Sai debuggare eccezioni complesse

๐ŸŽ“ 15. Conclusione

La gestione degli errori รจ una competenza fondamentale per ogni programmatore Python. Un codice robusto non solo funziona quando tutto va bene, ma sa gestire elegantemente le situazioni impreviste, fornendo feedback utile all'utente e mantenendo l'integritร  del sistema.

๐ŸŽฏ Punti Chiave da Ricordare
  1. Sii Specifico: Cattura sempre eccezioni specifiche, mai generiche
  2. Fail Fast: Rileva gli errori il prima possibile
  3. Clean Resources: Usa sempre finally o context manager
  4. Log Everything: Registra tutti gli errori significativi
  5. User Friendly: Messaggi di errore chiari per gli utenti
  6. Developer Friendly: Stack trace e dettagli per i developer
  7. Test Errors: Testa anche i casi di errore
  8. Document: Documenta le eccezioni che sollevi
๐Ÿ’ช Prossimi Passi

Per consolidare quanto appreso:

  1. Completa tutti gli esercizi proposti
  2. Rivedi il tuo codice esistente e migliora la gestione errori
  3. Implementa un sistema di logging nella tua applicazione
  4. Crea una gerarchia di eccezioni personalizzate
  5. Studia come gestiscono gli errori progetti open source famosi
  6. Pratica con casi reali: API, database, file I/O

Buon coding e... gestisci gli errori con saggezza! ๐Ÿโœจ